home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / name < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  49.4 KB  |  1,365 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) name.gawk 2.3 97/06/11
  4. # 90/06/01 john h. dubois iii (john@armory.com)
  5. # 90/11/14 removed ksh-specific code
  6. # 91/06/30 removed dependency on having variable assignments given
  7. #          on awk command line set before execution of BEGIN block
  8. #          (doesn't work in some awks)
  9. # 91/12/07 added PATH setting,
  10. #          added code to get rid of phone number in GCOS field
  11. # 92/05/02 converted to #!awk script
  12. # 96/06/18 Added all options.
  13. # 96/06/23 Added g option.
  14. # 96/09/10 2.0: Use pwent library.  Added ec options.
  15. # 96/11/17 Let UIDs be given.  Added x option.
  16. # 96/12/17 Added rsGanC options, config file, and home dir search.
  17. # 97/01/07 Added P option.
  18.  
  19. BEGIN {
  20.     Name = "name"
  21.     Usage = "Usage: " Name " [-ahgecCprsGn] <user> ..."
  22.     rcFile = "." Name "rc"
  23.     ARGC = Opts(Name,Usage,"apPecCgxrsGh",1,"~/" rcFile ":$UHOME/" rcFile,
  24.     "AUTOGCOS,ALLFIELDS,VERBOSE,EXACT,CASE,AUTOCASE",0,"n",0,"","g,s,G;p,P")
  25.     if ("h" in Options) {
  26.     printf \
  27. "%s: Print users' real names from /etc/passwd.\n"\
  28. "%s\n"\
  29. "The \"real name\" (the GCOS field from /etc/passwd) for each named user is\n"\
  30. "printed, in the format\n"\
  31. "user<tab>real-name\n"\
  32. "If a user name consists entirely of digits and no options (except -p) are\n"\
  33. "given, the name is taken to be a UID to search for.\n"\
  34. "If a user name contains any '/' characters and no options (except -p) are\n"\
  35. "given, the name is taken to be a home directory to search for.\n"\
  36. "Options:\n"\
  37. "Some of the following options can also be set by putting variables in a\n"\
  38. "configuration file named %s.  This file is searched for first in the home\n"\
  39. "directory of the invoking user and then, if the environment variable UHOME\n"\
  40. "is set, in the directory specified by that variable.  If both files exist,\n"\
  41. "variables assignments in the first file override those in the second file.\n"\
  42. "Variables are set by putting the indicated variable name in the file.  To\n"\
  43. "turn off a flag option in the first file and prevent it from being set in\n"\
  44. "the second file, assign it a value of 0.  e.g. if FOO is set in the first\n"\
  45. "file, FOO=0 in the second file will override it.  Options can be turned\n"\
  46. "off on the command line by following them immediately with \"-\", e.g. -v-\n"\
  47. "to turn off the v option in such a way that it cannot be turned on in a\n"\
  48. "config file.  Variable names appear in parentheses in the option\n"\
  49. "descriptions.\n"\
  50. "-h: Print this help.\n"\
  51. "-g: Search for names in the GCOS (\"real name\") field instead of the\n"\
  52. "    account name field.  The search is not case sensitive.  Unlike\n"\
  53. "    searches by account name, the name need not match the entire field.\n"\
  54. "    The words of each name given must occur in the same order in a GCOS\n"\
  55. "    field in order for a match to occur.  Multiple password file entries\n"\
  56. "    may be matched by one name.  If multiple words of a name are given,\n"\
  57. "    they must be quoted to form a single argument.  Example:\n"\
  58. "    %s -g 'john somebody'\n"\
  59. "-r: Treat the given names as unanchored regular expressions.  Names are\n"\
  60. "    compared to the same field that would be searched without -r.  With\n"\
  61. "    this option, the meaning of -e is changed to mean that the regular\n"\
  62. "    expression should be anchored at the start and end, and the meaning\n"\
  63. "    of -c is changed to mean that the match should be case sensitive.\n"\
  64. "-a: If a name is not found as a user name, search for it in the GCOS field\n"\
  65. "    as though -g had been given.  (AUTOGCOS)\n"\
  66. "-e: An exact match of the full GCOS field is required.  (EXACT)\n"\
  67. "-c: Matches against the GCOS field are made case sensitive. (CASE)\n"\
  68. "-C: Like -c, except that a match is only case sensitive for names that\n"\
  69. "    are given with some characters in upper case.  (AUTOCASE)\n"\
  70. "If -e, -c, or -C is given on the command line and -r is not given, the -g\n"\
  71. "option is turned on.  If the e, c, or C option is turn on by puttings its\n"\
  72. "variable in a config file, they only have an effect if -r or -g is given.\n"\
  73. "-s: Take the command line arguments to be the names of login shells, and\n"\
  74. "    print users who have any of the shells.\n"\
  75. "-G: Take the command line arguments to be the names or numeric IDs of\n"\
  76. "    groups, and print users who have any of them as their login group.\n"\
  77. "-n: Do not read the configuration file.\n"\
  78. "-p: Print entire password database lines for each user.  (ALLFIELDS)\n"\
  79. "-P: Print all password database fields (except the password) for each user\n"\
  80. "    in a more readable format, with headers.  Fields may be truncated so\n"\
  81. "    that each record fits on an 80-character line.  (VERBOSE)\n",
  82.     Name,Usage,Name,Name
  83.     exit(0)
  84.     }
  85.     Debug = "x" in Options
  86.     Verbose = "P" in Options
  87.     WholePasswd = "p" in Options
  88.  
  89.     autoGCOS = "a" in Options
  90.     CaseSensitive = "c" in Options
  91.     Full = "e" in Options
  92.     regex = "r" in Options
  93.     autoCase = "C" in Options
  94.     GCOS = ("g" in Options) || !regex && (CmdLineOpt(Options,"e") || \
  95.     CmdLineOpt(Options,"C") || CmdLineOpt(Options,"c"))
  96.  
  97.     ReadPasswd()    # Set PW_ values
  98.     if ("s" in Options)
  99.     fieldReq = PW_SHELL
  100.     else if (GCOS)
  101.     fieldReq = PW_GCOS
  102.     else if ("G" in Options) {
  103.     fieldReq = PW_GID
  104.     getGroupMap(gMap)
  105.     for (i = 1; i < ARGC; i++)
  106.         if ((user = ARGV[i]) !~ /^[0-9]+$/)
  107.         if (user in gMap)
  108.             ARGV[i] = gMap[user]
  109.         else {
  110.             errCt++
  111.             printf "%s: No group with this name: %s\n",Name,
  112.             user > "/dev/stderr"
  113.             delete ARGV[i]
  114.         }
  115.     if (errCt == ARGC-1)
  116.         exit 1
  117.     }
  118.     for (argnum = 1; argnum < ARGC; argnum++) {
  119.     if (!(argnum in ARGV))
  120.         continue
  121.     user = ARGV[argnum]
  122.     if (fieldReq)
  123.         Field = fieldReq
  124.     else if (user ~ /^[0-9]+$/)
  125.         Field = PW_UID
  126.     else if (user ~ "/")
  127.         Field = PW_HOME
  128.     else
  129.         Field = PW_NAME
  130.     if (Debug)
  131.         printf "%s: Searching for user: %s\n",Name,user > "/dev/stderr"
  132.     if (PWSearch(\
  133.     user,Field,Entries,autoGCOS,!CaseSensitive,autoCase,regex,Full)) {
  134.         if (Debug)
  135.         printf "%d entries returned by PWSearch().\n",
  136.         NumElem(Entries) > "/dev/stderr"
  137.         for (i = 1; i in Entries; i++)
  138.         printEntry(Entries[i],WholePasswd,Verbose)
  139.     }
  140.     else
  141.         printf "%s: No user with this %s: %s\n",Name,
  142.         (Field == PW_NAME && autoGCOS) ? \
  143.         pwFieldNames[PW_NAME] " or " pwFieldNames[PW_GCOS] : \
  144.         pwFieldNames[Field],user > "/dev/stderr"
  145.     }
  146.     exit 0
  147. }
  148.  
  149. # PWSearch: find entries in password file that match given criteria.
  150. # pat: Fixed string or pattern to compare to a password field.
  151. # Field: which password field to compare pat to, from the PW_* set.
  152. # Entries[]: The indexes of matching entries in the password database are
  153. #     returned as values in Entries[].  The index for each value in an
  154. #     integer giving the order in which it was found (starting with 1).
  155. # autoGCOS: If Field is PW_NAME and autoGCOS is true, pat is first searched
  156. #     for in the name field; if it is not found there, it is searched for in
  157. #     the "real name" field (part of the GCOS field).
  158. # ignoreCase: make matches be independent of case.
  159. # autoCase: If ignoreCase is false and autoCase is true, then matches are case
  160. #     case independent only if there are no upper case letters in pat.
  161. # regex: pat is treated as a regular expression and compared to the "real name"
  162. #     field.
  163. # Full: If regex is true, it is required to match the entire "real name" field.
  164. # Globals: Debug, pwFieldNames, PW_*
  165. # Return value: number of matching entries found (the highest index in
  166. #     Entries[]).
  167. function PWSearch(pat,Field,Entries,autoGCOS,ignoreCase,autoCase,regex,Full,
  168. oIC,PWEnt,num) {
  169.     split("",Entries)    # empty
  170.     if (ignoreCase && autoCase)
  171.     ignoreCase = (pat !~ /[A-Z]/)
  172.     num = 0
  173.     if (Field == PW_NAME && autoGCOS)
  174.     if (num = PWSearch(pat,Field,Entries,0,ignoreCase,0,regex,Full))
  175.         return num
  176.     else {
  177.         Field = PW_GCOS
  178.         if (Debug)
  179.         printf "No match for '%s' in name field, trying GCOS...\n",
  180.         pat > "/dev/stderr"
  181.     }
  182.     if (Debug)
  183.     printf "Searching for %s in \"%s\" field\n",
  184.     pat,pwFieldNames[Field] > "/dev/stderr"
  185.     if (regex) {
  186.     oIC = IGNORECASE
  187.     IGNORECASE = ignoreCase
  188.     if (Full)
  189.         pat = "^(" pat ")$"
  190.     setpwent()
  191.     while (getpwent(PWEnt,0,PW_REAL) != ":")
  192.         if (PWEnt[Field] ~ pat)
  193.         Entries[++num] = PWEnt[PW_RECORD]
  194.     IGNORECASE = oIC
  195.     }
  196.     else {
  197.     if (!getpw(Field,pat,PWEnt,"",0,ignoreCase,Full))
  198.         return 0
  199.     Entries[++num] = PWEnt[PW_RECORD]
  200.     while (getpw(Field,pat,PWEnt,"",1,ignoreCase,Full))
  201.         Entries[++num] = PWEnt[PW_RECORD]
  202.     }
  203.     if (Debug)
  204.     printf "Got %d match(es).\n",num > "/dev/stderr"
  205.     return num
  206. }
  207.  
  208. function trunc(s,len) {
  209.     if (length(s) > len)
  210.     s = substr(s,1,len-1) ">"
  211.     return s
  212. }
  213.  
  214. # Globals: HeaderPrinted, PW_*
  215. function printEntry(i,WholePasswd,Verbose,
  216. Format,Name,Home,Shell,PWEnt,line) {
  217.     line = PWLines[i]
  218.     if (WholePasswd)
  219.     print line
  220.     else {
  221.     PWGetFields(line,PWEnt,PW_REAL)
  222.     if (Verbose) {
  223.         Format = "%-8s %5s %5s  %-28s %-12s %s\n"
  224.         if (!HeaderPrinted) {
  225.         printf Format,"User","UID","LGID",
  226.         "Name            Office","Home dir","Login shell"
  227.         HeaderPrinted = 1
  228.         }
  229.         # Let name overflow into office field
  230.         Name = PWEnt[PW_REAL]
  231.         if (PWEnt[PW_OFFICE] != "")
  232.         Name = sprintf("%-15s %s",Name,PWEnt[PW_OFFICE])
  233.         printf Format,PWEnt[PW_NAME],PWEnt[PW_UID],PWEnt[PW_GID],
  234.         trunc(Name,28),trunc(PWEnt[PW_HOME],12),trunc(PWEnt[PW_SHELL],15)
  235.     }
  236.     else
  237.         printf "%s\t%s\n",PWEnt[PW_NAME],PWEnt[PW_REAL]
  238.     }
  239. }
  240.  
  241. # Globals: FS, $*
  242. function getGroupMap(map,  ret,count,ofs) {
  243.     ofs = FS
  244.     FS = ":"
  245.     while ((ret = (getline < "/etc/group")) == 1) {
  246.     map[$1] = $3
  247.     count++
  248.     }
  249.     close("/etc/group")
  250.     FS = ofs
  251.     return ret ? -1 : count
  252. }
  253.  
  254. ### Begin pwent library
  255.  
  256. # @(#) pwent.awk 1.3 96/12/15
  257. # 92/08/10 john h. dubois III (john@armory.com)
  258. # 93/12/13 fixed to not clobber $*
  259. # 96/01/05 Send error messages to /dev/stderr
  260. # 96/05/24 Let getpwnam() return a specific field if requested.
  261. #          Added PW_REAL and PW_OFFICE.
  262. # 96/06/03 Added Type field to getpwent()
  263. # 96/06/24 Allow a Field to be requested for getpwent() also.
  264. # 96/06/29 Added PW_RECORD, and getpwreal().
  265. #          Changed PWLines to be index by record number instead of name.
  266. # 96/11/17 Added getpwuid()
  267. # 96/12/15 Added pwFieldNames, and pwent() as general select-by-field routine.
  268.  
  269. # Require: ReadShells()
  270.  
  271. # getpwent, getpwnam: get an entry from the passwd file.
  272. # Each of the following passwd functions returns an array which contains
  273. # a passwd file entry.  The array contains the fields of the entry.
  274. # Global variables:
  275. # The following variables are defined with the values of the indexes of the
  276. # entries: PW_NAME, PW_PASSWORD, PW_UID, PW_GID, PW_GCOS, PW_HOME, PW_SHELL
  277. # PWLines[] contains the lines of the password file, indexed by record number,
  278. # starting with 1.
  279. # _pwNames[] is a mapping of name to passwd record number.
  280. # pwFieldNames[field-index] is set to a short description of each field.
  281. # getpwentNum is the number of the next entry to be returned by getpwent().
  282.  
  283. # Left FS global because making it local does not work in gawk.
  284. function ReadPasswd(  User,Line,i,Ind,ret) {
  285.     if (PW_Name)
  286.     return 1
  287.     PW_NAME = 1
  288.     PW_PASSWORD = 2
  289.     PW_UID = 3
  290.     PW_GID = 4
  291.     PW_GCOS = 5
  292.     PW_HOME = 6
  293.     PW_SHELL = 7
  294.     PW_REAL = -1    # for PWGetFields()
  295.     PW_OFFICE = -2
  296.     PW_RECORD = -3
  297.  
  298.     split(\
  299.     "user name,password,UID,login GID,real name,home directory,login shell",
  300.     pwFieldNames,",")
  301.     getpwentNum = 1
  302.     while ((ret = (getline Line < "/etc/passwd")) == 1) {
  303.     PWLines[++Ind] = Line
  304.     _pwNames[substr(Line,1,index(Line,":")-1)] = Ind
  305.     }
  306.     _num_pw = Ind
  307.     close("/etc/passwd")
  308.     if (ret) {
  309.     printf "ReadPasswd(): Could not open /etc/passwd: %s\n",
  310.     ERRNO > "/dev/stderr"
  311.     return 0
  312.     }
  313.     return 1
  314. }
  315.  
  316. # setpwent resets the passwd file entry pointer used by getpwent
  317. # to the first entry.
  318. function setpwent() {
  319.     getpwentNum = 1
  320. }
  321.  
  322. # getpwent sets PWEnt to the next entry in the passwd file.
  323. # If Type is set to -1, the entry for the next "real" user is returned (others
  324. # are skipped over), where a real user is a user whose login shell is listed in
  325. # /etc/shells.  This requires the ReadShells() function.  Other values for
  326. # Type are not yet defined and are ignored.
  327. # If the last entry has already been returned, 0 is return if Field is null,
  328. # ":" if not.
  329. # If the entry for the next real user has been requested and /etc/shells
  330. # cannot be read, -1 is returned if Field is null, "\n" if not.
  331. # See PWGetFields() for other return values and the meaning of the Field
  332. # parameter.
  333. function getpwent(PWEnt,Type,Field,  entNum) {
  334.     if (!PW_NAME)
  335.     ReadPasswd()
  336.     if (!(getpwentNum in PWLines))
  337.     return Field ? ":" : 0
  338.     if (Type == -1) {
  339.     if (!_DidReadShells && ReadShells(LoginShells) == -1)
  340.         return Field ? "\n" : -1
  341.     split(PWLines[getpwentNum++],PWEnt,":")
  342.     while (!(PWEnt[PW_SHELL] in LoginShells)) {
  343.         if (!(getpwentNum in PWLines))
  344.         return Field ? ":" : 0
  345.         split(PWLines[getpwentNum++],PWEnt,":")
  346.     }
  347.     return PWGetFields("",PWEnt,Field,getpwentNum - 1)
  348.     }
  349.     else {
  350.     entNum = getpwentNum
  351.     return PWGetFields(PWLines[getpwentNum++],PWEnt,Field,entNum)
  352.     }
  353. }
  354.  
  355. # PWGetFields() splits PWLine into PWEnt[], and optionally returns a field
  356. # from it.  If PWLine is null, PWEnt[] is assumed to have already been filled
  357. # in with a password entry.
  358. # If Field is not passed or is null, the return value is 1.
  359. # If Field is non-null, it should a PW_ value.  In this case, the value of the
  360. # requested field is returned.
  361. # If entNum is nonzero, it is the value that PWEnt[PW_RECORD] should be set to.
  362. # It should be the index in PWLines[] of the record being processed.
  363. # In addition to the PW_ values used by the rest of the functions in this
  364. # library, this function can be passed PW_REAL and PW_OFFICE.
  365. # PW_REAL will get the part of the GCOS field before the first comma.
  366. # PW_OFFICE will get the part of the GCOS field after the first comma.
  367. # If either of these is requested, both values will also be assigned to their
  368. # indices in PWEnt[], unless there is no comma in the GCOS field, in which case
  369. # PW_OFFICE will not be set.
  370. # NOTE: since the global field names are set in ReadShells(), it must be
  371. # executed before any of the field name can be passed.
  372. function PWGetFields(PWLine,PWEnt,Field,entNum,  gcos,ind) {
  373.     if (PWLine != "")
  374.     split(PWLine,PWEnt,":")
  375.     if (entNum)
  376.     PWEnt[PW_RECORD] = entNum
  377.     if (!Field)
  378.     return 1
  379.     if (Field < 0) {
  380.     if (ind = index(gcos = PWEnt[PW_GCOS],",")) {
  381.         PWEnt[PW_OFFICE] = substr(gcos,ind+1)
  382.         PWEnt[PW_REAL] = substr(gcos,1,ind-1)
  383.     }
  384.     else
  385.         PWEnt[PW_REAL] = gcos
  386.     }
  387.     return PWEnt[Field]
  388. }
  389.  
  390. # getpwnam sets PWEnt to the passwd entry for login name Name.
  391. # If Next is true or Name does not exist in the password file, the return value
  392. # is ":" if Field was passed, 0 if not.
  393. # For other return values and parameter explanation, see PWGetFields()
  394. function getpwnam(Name,PWEnt,Field,Next) {
  395.     if (!PW_NAME)
  396.     ReadPasswd()
  397.     if (!Next && Name in _pwNames)
  398.     return PWGetFields(PWLines[_pwNames[Name]],PWEnt,Field,_pwNames[Name])
  399.     else
  400.     return Field ? ":" : 0
  401. }
  402.  
  403. # Build uid->pw-index and home->pw-index
  404. # Globals: _pwIndexes[]
  405. function MakeInd(  Elem,Ind,Line,i,Fields,f) {
  406.     Fields[PW_UID]
  407.     Fields[PW_HOME]
  408.     Fields[PW_GID]
  409.     Fields[PW_SHELL]
  410.     for (Ind = 1; Ind in PWLines; Ind++) {
  411.     Line = PWLines[Ind]
  412.     split(Line,Elem,":")
  413.     for (f in Fields) {
  414.         i = f ":" Elem[f]
  415.         if (i in _pwIndexes)
  416.         _pwIndexes[i] = _pwIndexes[i] "," Ind
  417.         else
  418.         _pwIndexes[i] = Ind
  419.     }
  420.     }
  421.     IndDone = 1
  422. }
  423.  
  424. # getpw sets PWEnt to the first passwd entry whose FieldNum'th field is equal
  425. # to Value.  FieldNum may select any field except the password field.
  426. # If Next is true, Value is ignored and the next entry in the password file
  427. # that has the same FieldNum field as the last one requested is returned.
  428. # See getpwnam() for return values and the meaning of the Field param.
  429. # IgnoreCase and Full are used only for PW_GCOS searches; see getpwreal()
  430. # for their meaning.
  431. # Uses globals _pwMatches[] to track matches between calls.
  432. function getpw(FieldNum,Value,PWEnt,Field,Next,IgnoreCase,Full,  elem,i,n) {
  433.     if (FieldNum == PW_GCOS)
  434.     return getpwreal(Value,PWEnt,Field,Next,IgnoreCase,Full)
  435.     if (FieldNum == PW_NAME)
  436.     return getpwnam(Value,PWEnt,Field,Next)
  437.     if (Next) {
  438.     if ((FieldNum,_pwMatches[FieldNum,"cur"]) in _pwMatches) {
  439.         i = _pwMatches[FieldNum,_pwMatches[FieldNum,"cur"]++]
  440.         return PWGetFields(PWLines[i],PWEnt,Field,i)
  441.     }
  442.     }
  443.     else {
  444.     if (!IndDone)
  445.         MakeInd()
  446.     Value = FieldNum ":" Value
  447.     if (Value in _pwIndexes) {
  448.         n = split(_pwIndexes[Value],elem,",")
  449.         for (i = 1; i <= n; i++)
  450.         _pwMatches[FieldNum,i] = elem[i]
  451.         delete _pwMatches[FieldNum,n+1]    # so we know when to end
  452.         _pwMatches[FieldNum,"cur"] = 2
  453.         return PWGetFields(PWLines[elem[1]],PWEnt,Field,elem[1])
  454.     }
  455.     }
  456.     return Field ? ":" : 0
  457. }
  458.  
  459. function getpwhome(home,PWEnt,Field,Next) {
  460.     if (!PW_NAME)    # set PW_ values
  461.     ReadPasswd()
  462.     return getpw(PW_HOME,home,PWEnt,Field,Next)
  463. }
  464.  
  465. function getpwuid(UID,PWEnt,Field,Next) {
  466.     if (!PW_NAME)    # set PW_ values
  467.     ReadPasswd()
  468.     return getpw(PW_UID,(UID+0),PWEnt,Field,Next)
  469. }
  470.  
  471. # Make an index by real name.  For each passwd file entry, the real-name
  472. # is lowercased and split into components on non-alphanums.   The passwd entry
  473. # index that the name came from is added to the value of each such component
  474. # in the global _RealInd[].  The indexes stored this way are separated by
  475. # commas.  If the real-name contains no alphanums, its index is stored under
  476. # the null index.
  477. function _makeRealInd(  PWEnt,ret,Elem,nelem,i,Component) {
  478.     setpwent()
  479.     while ((ret = getpwent(PWEnt,"",PW_REAL)) != ":") {
  480.     nelem = split(tolower(ret),Elem,/[^a-z0-9]+/)
  481.     for (i = 1; i <= nelem; i++) {
  482.         Component = Elem[i]
  483.         if (Component == "" && nelem > 1)
  484.         continue
  485.         if (Component in _RealInd)
  486.         _RealInd[Component] = _RealInd[Component] "," PWEnt[PW_RECORD]
  487.         else
  488.         _RealInd[Component] = PWEnt[PW_RECORD]
  489.     }
  490.     }
  491.     _realIndDone = 1
  492. }
  493.  
  494. # Make Name into a pattern that will match a name that contains all of the
  495. # same name components (sequences of alphanums) in the same order.  If Name
  496. # contains no name components, a null string is returned.
  497. function MakeNamePat(Name,  Elem,nelem,i,Pat,e) {
  498.     nelem = split(Name,Elem,/[^a-zA-Z0-9]+/)
  499.     for (i = 1; i <= nelem; i++) {
  500.     if ((e = Elem[i]) == "")
  501.         continue
  502.     if (Pat == "")
  503.         Pat = "(^|[^a-zA-Z0-9])" e
  504.     else
  505.         Pat = Pat "[^a-zA-Z0-9](.*[^a-zA-Z0-9])?" e
  506.     }
  507.     if (Pat == "")    # If Name contained no alphanums...
  508.     return ""
  509.     Pat = Pat "([^a-zA-Z0-9]|$)"
  510.     return Pat
  511. }
  512.  
  513. # getpwgreal sets PWEnt to the first passwd entry whose PW_REAL (see
  514. # PWGetFields()) field matches Real.  Matching occurs if the alphanumeric
  515. # components of Real occur in the same order in the entry.  Non-alphanums are
  516. # ignored.  All of the components in Real must occur in the entry, but not all
  517. # of the components in the entry must occur in Real.
  518. # If the given name does not exist in the password file,
  519. # the return value is ":" if Field was passed, 0 if not.
  520. # If Next is true, getpwreal() sets PWEnt to the next passwd entry whose
  521. # PW_REAL field matches the last previous Real parameter passed.
  522. # In this case,  if the last entry has already been returned,
  523. # the return value is ":" if Field was passed, 0 if not.
  524. # Different IgnoreCase and Full parameters may be given when doing a Next
  525. # search.  Both must always be passed; they do not default to the original
  526. # values when doing a Next search.  The only parameter ignored when doing a
  527. # Next search is Real.
  528. # If IgnoreCase is true, case is ignored when searching.
  529. # If Full is true, a match of the full name is required (including any
  530. # punctuation).
  531. # For successful return values and Field parameter explanation,
  532. # see PWGetFields()
  533. # Globals: For the Next search, between invokations these varies store values:
  534. # _getpwrealInd[]: The set of pw indices that matched the query.
  535. # _getpwrealIndInd: The next index in _getpwrealInd[] to look at.
  536. # _getpwrealReal: The Real value passed with the original query.
  537. # _getpwrealPat: Real converted to a component order search pattern.
  538. function getpwreal(Real,PWEnt,Field,Next,IgnoreCase,Full,  ind,name,Pat) {
  539.     if (!Next) {
  540.     if (!PW_NAME)
  541.         ReadPasswd()
  542.     if (!_realIndDone)
  543.         _makeRealInd()
  544.     _getpwrealReal = Real
  545.     _getpwrealPat = MakeNamePat(Real)
  546.     # Get first component from Real
  547.     Real = tolower(Real)
  548.     gsub("^[^a-z0-9]+","",Real)
  549.     gsub("[^a-z0-9].*","",Real)
  550.     if (!(Real in _RealInd))
  551.         return Field ? ":" : 0
  552.     split(_RealInd[Real],_getpwrealInd,",")
  553.     _getpwrealIndInd = 1
  554.     }
  555.     if (Full)
  556.     Pat = _getpwrealReal
  557.     else
  558.     Pat = _getpwrealPat
  559.     if (IgnoreCase)
  560.     Pat = tolower(Pat)
  561.     while (_getpwrealIndInd in _getpwrealInd) {
  562.     ind = _getpwrealInd[_getpwrealIndInd++]
  563.     name = PWGetFields(PWLines[ind],PWEnt,PW_REAL,ind)
  564.     if (IgnoreCase)
  565.         name = tolower(name)
  566.     if (Full ? (name == Pat) : (name ~ Pat))
  567.         return PWGetFields("",PWEnt,Field,ind)
  568.     }
  569.     return Field ? ":" : 0
  570. }
  571.  
  572. ### End pwent library
  573. ### Start of ProcArgs library
  574. # @(#) ProcArgs 1.11 96/12/08
  575. # 92/02/29 john h. dubois iii (john@armory.com)
  576. # 93/07/18 Added "#" arg type
  577. # 93/09/26 Do not count -h against MinArgs
  578. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  579. #          Removed meaning of "+" or "-" by itself.
  580. # 94/03/08 Added & option and *()< option types.
  581. # 94/04/02 Added NoRCopt to Opts()
  582. # 94/06/11 Mark numeric variables as such.
  583. # 94/07/08 Opts(): Do not require any args if h option is given.
  584. # 94/09/23 Fixed bug that caused fail if -opt<value> given as last arg.
  585. # 95/01/22 Record options given more than once.  Record option num in argv.
  586. # 95/06/08 Added ExclusiveOptions().
  587. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  588. #          Expand $VARNAME at the start of its filenames.
  589. #          Let varname=0 and -option- turn off an option.
  590. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  591. #          of the vars should be searched for in the environment.
  592. #          Check for duplicate rcfiles.
  593. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  594. #          now return various negatives values on error, not just -1, and
  595. #          Opts() may set Err to various positive values, not just 1.
  596. #          Added AllowUnrecOpt.
  597. # 96/05/23 Check type given for & option
  598. # 96/06/15 Re-port to awk
  599. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  600. #          used by other functions.
  601. # 96/10/15 Added OptChars
  602. # 96/11/01 Added exOpts arg to Opts()
  603. # 96/11/16 Added ; type
  604. # 96/12/08 Added Opt2Set() & Opt2Sets()
  605.  
  606. # optlist is a string which contains all of the possible command line options.
  607. # A character followed by certain characters indicates that the option takes
  608. # an argument, with type as follows:
  609. # :    String argument
  610. # ;    Non-empty string argument
  611. # *    Floating point argument
  612. # (    Non-negative floating point argument
  613. # )    Positive floating point argument
  614. # #    Integer argument
  615. # <    Non-negative integer argument
  616. # >    Positive integer argument
  617. # The only difference the type of argument makes is in the runtime argument
  618. # error checking that is done.
  619.  
  620. # The & option is a special case used to get numeric options without the
  621. # user having to give an option character.  It is shorthand for [-+.0-9].
  622. # If & is included in optlist and an option string that begins with one of
  623. # these characters is seen, the value given to "&" will include the first
  624. # char of the option.  & must be followed by a type character other than ":"
  625. # or ";".
  626. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  627.  
  628. # Strings in argv[] which begin with "-" or "+" are taken to be
  629. # strings of options, except that a string which consists solely of "-"
  630. # or "+" is taken to be a non-option string; like other non-option strings,
  631. # it stops the scanning of argv and is left in argv[].
  632. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  633. # If an option takes an argument, the argument may either immediately
  634. # follow it or be given separately.
  635. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  636. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  637. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  638. # this feature had a bug that caused problems in some cases.  See the OptChars
  639. # parameter to explicitly set the option-specifier characters.
  640.  
  641. # If an option that does not take an argument is given,
  642. # an index with its name is created in Options and its value is set to the
  643. # number of times it occurs in argv[].
  644.  
  645. # If an option that does take an argument is given, an index with its name is
  646. # created in Options and its value is set to the value of the argument given
  647. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  648. # If an option that takes an argument is given more than once,
  649. # Options[option-name,"count"] is incremented, and the value is assigned to
  650. # the index (option-name,instance) where instance is 2 for the second occurance
  651. # of the option, etc.
  652. # In other words, the first time an option with a value is encountered, the
  653. # value is assigned to an index consisting only of its name; for any further
  654. # occurances of the option, the value index has an extra (count) dimension.
  655.  
  656. # The sequence number for each option found in argv[] is stored in
  657. # Options[option-name,"num",instance], where instance is 1 for the first
  658. # occurance of the option, etc.  The sequence number starts at 1 and is
  659. # incremented for each option, both those that have a value and those that
  660. # do not.  Options set from a config file have a value of 0 assigned to this.
  661.  
  662. # Options and their arguments are deleted from argv.
  663. # Note that this means that there may be gaps left in the indices of argv[].
  664. # If compress is nonzero, argv[] is packed by moving its elements so that
  665. # they have contiguous integer indices starting with 0.
  666. # Option processing will stop with the first unrecognized option, just as
  667. # though -- was given except that unlike -- the unrecognized option will not be
  668. # removed from ARGV[].  Normally, an error value is returned in this case.
  669. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  670. # be found, so the number of remaining arguments is returned instead.
  671. # If OptChars is not a null string, it is the set of characters that indicate
  672. # that an argument is an option string if the string begins with one of the
  673. # characters.  A string consisting solely of two of the same option-indicator
  674. # characters stops the scanning of argv[].  The default is "-+".
  675. # argv[0] is not examined.
  676. # The number of arguments left in argc is returned.
  677. # If an error occurs, the global string OptErr is set to an error message
  678. # and a negative value is returned.
  679. # Current error values:
  680. # -1: option that required an argument did not get it.
  681. # -2: argument of incorrect type supplied for an option.
  682. # -3: unrecognized (invalid) option.
  683. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  684. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  685. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  686. {
  687. # ArgNum is the index of the argument being processed.
  688. # ArgsLeft is the number of arguments left in argv.
  689. # Arg is the argument being processed.
  690. # ArgLen is the length of the argument being processed.
  691. # ArgInd is the position of the character in Arg being processed.
  692. # Option is the character in Arg being processed.
  693. # Pos is the position in OptList of the option being processed.
  694. # NumOpt is true if a numeric option may be given.
  695.     ArgsLeft = argc
  696.     NumOpt = index(OptList,"&")
  697.     OptionNum = 0
  698.     if (OptChars == "")
  699.     OptChars = "-+"
  700.     while (OptChars != "") {
  701.     c = substr(OptChars,1,1)
  702.     OptChars = substr(OptChars,2)
  703.     OptCharSet[c]
  704.     OptTerm[c c]
  705.     }
  706.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  707.     Arg = argv[ArgNum]
  708.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  709.         break    # Not an option; quit
  710.     if (Arg in OptTerm) {
  711.         delete argv[ArgNum]
  712.         ArgsLeft--
  713.         break
  714.     }
  715.     ArgLen = length(Arg)
  716.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  717.         Option = substr(Arg,ArgInd,1)
  718.         if (NumOpt && Option ~ /[-+.0-9]/) {
  719.         # If this option is a numeric option, make its flag be & and
  720.         # its option string flag position be the position of & in
  721.         # the option string.
  722.         Option = "&"
  723.         Pos = NumOpt
  724.         # Prefix Arg with a char so that ArgInd will point to the
  725.         # first char of the numeric option.
  726.         Arg = "&" Arg
  727.         ArgLen++
  728.         }
  729.         # Find position of flag in option string, to get its type (if any).
  730.         # Disallow & as literal flag.
  731.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  732.         if (AllowUnrecOpt) {
  733.             Escape = 1
  734.             break
  735.         }
  736.         else {
  737.             OptErr = "Invalid option: " specGiven Option
  738.             return -3
  739.         }
  740.         }
  741.  
  742.         # Find what the value of the option will be if it takes one.
  743.         # NeedNextOpt is true if the option specifier is the last char of
  744.         # this arg, which means that if the option requires a value it is
  745.         # the next arg.
  746.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  747.         if (GotValue = ArgNum + 1 < argc)
  748.             Value = argv[ArgNum+1]
  749.         }
  750.         else {    # Value is included with option
  751.         Value = substr(Arg,ArgInd + 1)
  752.         GotValue = 1
  753.         }
  754.  
  755.         if (HadValue = AssignVal(Option,Value,Options,
  756.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  757.         specGiven)) {
  758.         if (HadValue < 0)    # error occured
  759.             return HadValue
  760.         if (HadValue == 2)
  761.             ArgInd++    # Account for the single-char value we used.
  762.         else {
  763.             if (NeedNextOpt) {    # option took next arg as value
  764.             delete argv[++ArgNum]
  765.             ArgsLeft--
  766.             }
  767.             break    # This option has been used up
  768.         }
  769.         }
  770.     }
  771.     if (Escape)
  772.         break
  773.     # Do not delete arg until after processing of it, so that if it is not
  774.     # recognized it can be left in ARGV[].
  775.     delete argv[ArgNum]
  776.     ArgsLeft--
  777.     }
  778.     if (compress != 0) {
  779.     dest = 1
  780.     src = argc - ArgsLeft + 1
  781.     for (count = ArgsLeft - 1; count; count--) {
  782.         ARGV[dest] = ARGV[src]
  783.         dest++
  784.         src++
  785.     }
  786.     }
  787.     return ArgsLeft
  788. }
  789.  
  790. # Assignment to values in Options[] occurs only in this function.
  791. # Option: Option specifier character.
  792. # Value: Value to be assigned to option, if it takes a value.
  793. # Options[]: Options array to return values in.
  794. # ArgType: Argument type specifier character.
  795. # GotValue: Whether any value is available to be assigned to this option.
  796. # Name: Name of option being processed.
  797. # OptionNum: Number of this option (starting with 1) if set in argv[],
  798. #     or 0 if it was given in a config file or in the environment.
  799. # SingleOpt: true if the value (if any) that is available for this option was
  800. #     given as part of the same command line arg as the option.  Used only for
  801. #     options from the command line.
  802. # specGiven is the option specifier character use, if any (e.g. - or +),
  803. # for use in error messages.
  804. # Global variables: OptErr
  805. # Return value: negative value on error, 0 if option did not require an
  806. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  807. # the arg.
  808. # Current error values:
  809. # -1: Option that required an argument did not get it.
  810. # -2: Value of incorrect type supplied for option.
  811. # -3: Bad type given for option &
  812. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  813. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  814.     # If option takes a value...    [
  815.     NumTypes = "*()#<>]"
  816.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  817.     OptErr = "Bad type given for & option"
  818.     return -3
  819.     }
  820.  
  821.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  822.     if (!GotValue) {
  823.         if (Name != "")
  824.         OptErr = "Variable requires a value -- " Name
  825.         else
  826.         OptErr = "option requires an argument -- " Option
  827.         return -1
  828.     }
  829.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  830.         OptErr = Err
  831.         return -2
  832.     }
  833.     # Mark this as a numeric variable; will be propogated to Options[] val.
  834.     if (ArgType != ":" && ArgType != ";")
  835.         Value += 0
  836.     if ((Instance = ++Options[Option,"count"]) > 1)
  837.         Options[Option,Instance] = Value
  838.     else
  839.         Options[Option] = Value
  840.     }
  841.     # If this is an environ or rcfile assignment & it was given a value...
  842.     else if (!OptionNum && Value != "") {
  843.     UsedValue = 1
  844.     # If the value is "0" or "-" and this is the first instance of it,
  845.     # do not set Options[Option]; this allows an assignment in an rcfile to
  846.     # turn off an option (for the simple "Option in Options" test) in such
  847.     # a way that it cannot be turned on in a later file.
  848.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  849.         Instance = 1
  850.     else
  851.         Instance = ++Options[Option]
  852.     # Save the value even though this is a flag
  853.     Options[Option,Instance] = Value
  854.     }
  855.     # If this is a command line flag and has a - following it in the same arg,
  856.     # it is being turned off.
  857.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  858.     UsedValue = 2
  859.     if (Option in Options)
  860.         Instance = ++Options[Option]
  861.     else
  862.         Instance = 1
  863.     Options[Option,Instance]
  864.     }
  865.     # If this is a flag assignment without a value, increment the count for the
  866.     # flag unless it was turned off.  The indicator for a flag being turned off
  867.     # is that the flag index has not been set in Options[] but it has an
  868.     # instance count.
  869.     else if (Option in Options || !((Option,1) in Options))
  870.     # Increment number of times this flag seen; will inc null value to 1
  871.     Instance = ++Options[Option]
  872.     Options[Option,"num",Instance] = OptionNum
  873.     return UsedValue
  874. }
  875.  
  876. # Option is the option letter
  877. # Value is the value being assigned
  878. # Name is the var name of the option, if any
  879. # ArgType is one of:
  880. # :    String argument
  881. # ;    Non-null string argument
  882. # *    Floating point argument
  883. # (    Non-negative floating point argument
  884. # )    Positive floating point argument
  885. # #    Integer argument
  886. # <    Non-negative integer argument
  887. # >    Positive integer argument
  888. # specGiven is the option specifier character use, if any (e.g. - or +),
  889. # for use in error messages.
  890. # Returns null on success, err string on error
  891. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  892.     if (ArgType == ":")
  893.     return ""
  894.     if (ArgType == ";") {
  895.     if (Value == "")
  896.         Err = "must be a non-empty string"
  897.     }
  898.     # A number begins with optional + or -, and is followed by a string of
  899.     # digits or a decimal with digits before it, after it, or both
  900.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  901.     Err = "must be a number"
  902.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  903.     Err = "may not include a fraction"
  904.     else if (ArgType ~ "[()<>]" && Value < 0)
  905.     Err = "may not be negative"
  906.     # (
  907.     else if (ArgType ~ "[)>]" && Value == 0)
  908.     Err = "must be a positive number"
  909.     if (Err != "") {
  910.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  911.     if (Name != "")
  912.         return ErrStr "variable " substr(Name,1,1) " " Err
  913.     else {
  914.         if (Option == "&")
  915.         Option = Value
  916.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  917.     }
  918.     }
  919.     else
  920.     return ""
  921. }
  922.  
  923. # Note: only the above functions are needed by ProcArgs.
  924. # The rest of these functions call ProcArgs() and also do other
  925. # option-processing stuff.
  926.  
  927. # Opts: Process command line arguments.
  928. # Opts processes command line arguments using ProcArgs()
  929. # and checks for errors.  If an error occurs, a message is printed
  930. # and the program is exited.
  931. #
  932. # Input variables:
  933. # Name is the name of the program, for error messages.
  934. # Usage is a usage message, for error messages.
  935. # OptList the option description string, as used by ProcArgs().
  936. # MinArgs is the minimum number of non-option arguments that this
  937. # program should have, non including ARGV[0] and +h.
  938. # If the program does not require any non-option arguments,
  939. # MinArgs should be omitted or given as 0.
  940. # rcFiles, if given, is a colon-seprated list of filenames to read for
  941. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  942. # by the value of the environment variable HOME.  If a filename begins with
  943. # $, the part from the character after the $ up until (but not including)
  944. # the first character not in [a-zA-Z0-9_] will be searched for in the
  945. # environment; if found its value will be substituted, if not the filename will
  946. # be discarded.
  947. # rcfiles are read in the order given.
  948. # Values given in them will not override values given on the command line,
  949. # and values given in later files will not override those set in earlier
  950. # files, because AssignVal() will store each with a different instance index.
  951. # The first instance of each variable, either on the command line or in an
  952. # rcfile, will be stored with no instance index, and this is the value
  953. # normally used by programs that call this function.
  954. # VarNames is a comma-separated list of variable names to map to options,
  955. # in the same order as the options are given in OptList.
  956. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  957. # searched for in the environment.  If set to -1, all values will be searched
  958. # for in the environment.  Values given in the environment will override
  959. # those given in the rcfiles but not those given on the command line.
  960. # NoRCopt, if given, is an additional letter option that if given on the
  961. # command line prevents the rcfiles from being read.
  962. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  963. # ExclusiveOptions() for a description of exOpts.
  964. # Special options:
  965. # If x is made an option and is given, some debugging info is output.
  966. # h is assumed to be the help option.
  967.  
  968. # Global variables:
  969. # The command line arguments are taken from ARGV[].
  970. # The arguments that are option specifiers and values are removed from
  971. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  972. # The number of elements in ARGV[] should be in ARGC.
  973. # After processing, ARGC is set to the number of elements left in ARGV[].
  974. # The option values are put in Options[].
  975. # On error, Err is set to a positive integer value so it can be checked for in
  976. # an END block.
  977. # Return value: The number of elements left in ARGV is returned.
  978. # Must keep OptErr global since it may be set by InitOpts().
  979. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  980. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  981.     if (MinArgs == "")
  982.     MinArgs = 0
  983.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  984.     optChars)
  985.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  986.     if (ArgsLeft >= 0) {
  987.         OptErr = "Not enough arguments"
  988.         Err = 4
  989.     }
  990.     else
  991.         Err = -ArgsLeft
  992.     printf "%s: %s.\nUse -h for help.\n%s\n",
  993.     Name,OptErr,Usage > "/dev/stderr"
  994.     exit 1
  995.     }
  996.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  997.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  998.     {
  999.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1000.     Err = -e
  1001.     exit 1
  1002.     }
  1003.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1004.     {
  1005.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1006.     Err = 1
  1007.     exit 1
  1008.     }
  1009.     return ArgsLeft
  1010. }
  1011.  
  1012. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1013. # <variable-name><assignment-char><value>.
  1014. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1015. # line and whitespace between the variable name and the assignment character) 
  1016. # is stripped.  Lines that do not contain an assignment operator or which
  1017. # contain a null variable name are ignored, other than possibly being noted in
  1018. # the return value.  If more than one assignment is made to a variable, the
  1019. # first assignment is used.
  1020. # Input variables:
  1021. # File is the file to read.
  1022. # Comment is the line-comment character.  If it is found as the first non-
  1023. #     whitespace character on a line, the line is ignored.
  1024. # Assign is the assignment string.  The first instance of Assign on a line
  1025. #     separates the variable name from its value.
  1026. # If StripWhite is true, whitespace around the value (whitespace between the
  1027. #     assignment char and trailing whitespace on the line) is stripped.
  1028. # VarPat is a pattern that variable names must match.  
  1029. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1030. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1031. #     a line; no assignment operator is needed.  These variables are set in
  1032. #     the output array with a null value.  Lines containing nothing but
  1033. #     whitespace are still ignored.
  1034. # Output variables:
  1035. # Values[] contains the assignments, with the indexes being the variable names
  1036. #     and the values being the assigned values.
  1037. # Lines[] contains the line number that each variable occured on.  A flag set
  1038. #     is record by giving it an index in Lines[] but not in Values[].
  1039. # Return value:
  1040. # If any errors occur, a string consisting of descriptions of the errors
  1041. # separated by newlines is returned.  In no case will the string start with a
  1042. # numeric value.  If no errors occur,  the number of lines read is returned.
  1043. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1044. FlagsOK,
  1045. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1046.     if (Comment != "")
  1047.     Comment = "^" Comment
  1048.     AssignLen = length(Assign)
  1049.     if (VarPat == "")
  1050.     VarPat = "."    # null varname not allowed
  1051.     while ((Status = (getline Line < File)) == 1) {
  1052.     LineNum++
  1053.     sub("^[ \t]+","",Line)
  1054.     if (Line == "")        # blank line
  1055.         continue
  1056.     if (Comment != "" && Line ~ Comment)
  1057.         continue
  1058.     if (Pos = index(Line,Assign)) {
  1059.         Var = substr(Line,1,Pos-1)
  1060.         Val = substr(Line,Pos+AssignLen)
  1061.         if (StripWhite) {
  1062.         sub("^[ \t]+","",Val)
  1063.         sub("[ \t]+$","",Val)
  1064.         }
  1065.     }
  1066.     else {
  1067.         Var = Line    # If no value, var is entire line
  1068.         Val = ""
  1069.     }
  1070.     if (!FlagsOK && Val == "") {
  1071.         Errs = Errs \
  1072.         sprintf("\nBad assignment on line %d of file %s: %s",
  1073.         LineNum,File,Line)
  1074.         continue
  1075.     }
  1076.     sub("[ \t]+$","",Var)
  1077.     if (Var !~ VarPat) {
  1078.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1079.         LineNum,File,Var)
  1080.         continue
  1081.     }
  1082.     if (!(Var in Lines)) {
  1083.         Lines[Var] = LineNum
  1084.         if (Pos)
  1085.         Values[Var] = Val
  1086.     }
  1087.     }
  1088.     if (Status)
  1089.     Errs = Errs "\nCould not read file " File
  1090.     close(File)
  1091.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1092. }
  1093.  
  1094. # Variables:
  1095. # Data is stored in Options[].
  1096. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1097. # Global vars:
  1098. # Sets OptErr.  Uses ENVIRON[].
  1099. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1100. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1101. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1102. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1103.     split("",filesRead,"")    # make awk know this is an array
  1104.     NumVars = split(VarNames,Vars,",")
  1105.     TypesInd = Ret = 0
  1106.     if (EnvSearch == -1)
  1107.     EnvSearch = NumVars
  1108.     for (i = 1; i <= NumVars; i++) {
  1109.     Var = Vars[i]
  1110.     CharOpt = substr(OptList,++TypesInd,1)
  1111.     if (CharOpt ~ "^[:;*()#<>&]$")
  1112.         CharOpt = substr(OptList,++TypesInd,1)
  1113.     Map[Var] = CharOpt
  1114.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1115.     # Do not overwrite entries from environment
  1116.     if (i <= EnvSearch && Var in ENVIRON &&
  1117.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1118.         return Err
  1119.     }
  1120.  
  1121.     numrcFiles = split(rcFiles,fNames,":")
  1122.     for (i = 1; i <= numrcFiles; i++) {
  1123.     rcFile = fNames[i]
  1124.     if (rcFile ~ "^~/")
  1125.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1126.     else if (rcFile ~ /^\$/) {
  1127.         rcFile = substr(rcFile,2)
  1128.         match(rcFile,"^[a-zA-Z0-9_]*")
  1129.         envvar = substr(rcFile,1,RLENGTH)
  1130.         if (envvar in ENVIRON)
  1131.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1132.         else
  1133.         continue
  1134.     }
  1135.     if (rcFile in filesRead)
  1136.         continue
  1137.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1138.     # may be the same
  1139.     filesRead[rcFile]
  1140.     if ("x" in Options)
  1141.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1142.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1143.     if (retStr > 0)
  1144.         READ_RCFILE = 1
  1145.     else if (ret != "") {
  1146.         OptErr = retStr
  1147.         Ret = -1
  1148.     }
  1149.     for (Var in Lines)
  1150.         if (Var in Map) {
  1151.         if ((Err = AssignVal(Map[Var],
  1152.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1153.         Var in Values,Var,0)) < 0)
  1154.             return Err
  1155.         }
  1156.         else {
  1157.         OptErr = sprintf(\
  1158.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1159.         Lines[Var],rcFile)
  1160.         Ret = -1
  1161.         }
  1162.     }
  1163.  
  1164.     if ("x" in Options)
  1165.     for (Var in Map)
  1166.         if (Map[Var] in Options)
  1167.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1168.         "/dev/stderr"
  1169.         else
  1170.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1171.     return Ret
  1172. }
  1173.  
  1174. # OptSets is a semicolon-separated list of sets of option sets.
  1175. # Within a list of option sets, the option sets are separated by commas.  For
  1176. # each set of sets, if any option in one of the sets is in Options[] AND any
  1177. # option in one of the other sets is in Options[], an error string is returned.
  1178. # If no conflicts are found, nothing is returned.
  1179. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1180. # the exclusions presented by the first set of sets (ab,def,g) if:
  1181. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1182. # (a or b is in Options[]) AND (g is in Options) OR
  1183. # (d, e, or f is in Options[]) AND (g is in Options)
  1184. # An error will be returned due to the exclusions presented by the second set
  1185. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1186. # todo: make options given on command line unset options given in config file
  1187. # todo: that they conflict with.
  1188. function ExclusiveOptions(OptSets,Options,
  1189. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1190. SetNum,OSetNum) {
  1191.     NumSetSets = split(OptSets,SetSets,";")
  1192.     # For each set of sets...
  1193.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1194.     # NumSets is the number of sets in this set of sets.
  1195.     NumSets = split(SetSets[SetSet],Sets,",")
  1196.     # For each set in a set of sets except the last...
  1197.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1198.         s1 = Sets[SetNum]
  1199.         L1 = length(s1)
  1200.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1201.         # If any of the options in this set was given, check whether
  1202.         # any of the options in the other sets was given.  Only check
  1203.         # later sets since earlier sets will have already been checked
  1204.         # against this set.
  1205.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1206.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1207.             s2 = Sets[OSetNum]
  1208.             L2 = length(s2)
  1209.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1210.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1211.                 ErrStr = ErrStr "\n"\
  1212.                 sprintf("Cannot give both %s and %s options.",
  1213.                 c1,c2)
  1214.             }
  1215.     }
  1216.     }
  1217.     if (ErrStr != "")
  1218.     return substr(ErrStr,2)
  1219.     return ""
  1220. }
  1221.  
  1222. # The value of each instance of option Opt that occurs in Options[] is made an
  1223. # index of Set[].
  1224. # The return value is the number of instances of Opt in Options.
  1225. function Opt2Set(Options,Opt,Set,  count) {
  1226.     if (!(Opt in Options))
  1227.     return 0
  1228.     Set[Options[Opt]]
  1229.     count = Options[Opt,"count"]
  1230.     for (; count > 1; count--)
  1231.     Set[Options[Opt,count]]
  1232.     return count
  1233. }
  1234.  
  1235. # The value of each instance of option Opt that occurs in Options[] that
  1236. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1237. # Other values are made indexes of Set[].
  1238. # The return value is the number of instances of Opt in Options.
  1239. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1240.     ret = Opt2Set(Options,Opt,aSet)
  1241.     for (value in aSet)
  1242.     if (substr(value,1,1) == "!")
  1243.         nSet[substr(value,2)]
  1244.     else
  1245.         Set[value]
  1246.     return ret
  1247. }
  1248.  
  1249. # Returns true if option Opt was given on the command line.
  1250. function CmdLineOpt(Options,Opt,  i) {
  1251.     for (i = 1; (Opt,"num",i) in Options; i++)
  1252.     if (Options[Opt,"num",i] != 0)
  1253.         return 1
  1254.     return 0
  1255. }
  1256. ### End of ProcArgs library
  1257. ### Begin set library
  1258. # 96/05/23 added return values  jhdiii
  1259. # 96/05/25 added set2list()
  1260. # 97/01/26 Added AOnly(), Exclusive()
  1261.  
  1262. # Return value: the number of new elements added to Inter
  1263. function Intersection(A,B,Inter,  Elem,Count) {
  1264.     for (Elem in A)
  1265.     if (Elem in B && !(Elem in Inter)) {
  1266.         Inter[Elem]
  1267.         Count++
  1268.     }
  1269.     return Count
  1270. }
  1271.  
  1272. # Any element that is in A or B but not both and which is not already in
  1273. # Excl is added to Excl.
  1274. # Return value: the number of new elements added to Excl
  1275. function Exclusive(A,B,Excl) {
  1276.     return AOnly(A,B,Excl) + AOnly(B,A,Excl)
  1277. }
  1278.  
  1279. # Any element that is in A and not in B or aOnly is added to aOnly.
  1280. # Return value: the number of new elements added to aOnly.
  1281. function AOnly(A,B,aOnly,  Elem,Count) {
  1282.     for (Elem in A)
  1283.     if (!(Elem in B) && !(Elem in aOnly)) {
  1284.         aOnly[Elem]
  1285.         Count++
  1286.     }
  1287.     return Count
  1288. }
  1289.  
  1290. # Return value: the number of new elements added to Both
  1291. function Union(A,B,Both) {
  1292.     return CopySet(A,Both) + CopySet(B,Both)
  1293. }
  1294.  
  1295. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  1296. # Return value: the number of elements deleted.
  1297. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  1298.     for (Elem in Subtrahend)
  1299.     if (Elem in Minuend) {
  1300.         delete Minuend[Elem]
  1301.         nDel++
  1302.     }
  1303.     return nDel
  1304. }
  1305.  
  1306. # Return value: the number of new elements added to To
  1307. function CopySet(From,To,  Elem,n) {
  1308.     for (Elem in From)
  1309.     if (!(Elem in To)) {
  1310.         To[Elem]
  1311.         n++
  1312.     }
  1313.     return n
  1314. }
  1315.  
  1316. # Returns 1 if Set is empty, 0 if not.
  1317. function IsEmpty(Set,  i) {
  1318.     for (i in Set)
  1319.     return 0
  1320.     return 1
  1321. }
  1322.  
  1323. # MakeSet: make a set from a list.
  1324. # An index with the name of each element of the list is created in the given
  1325. # array.
  1326. # Input variables:
  1327. # Elements is a string containing the list of elements.
  1328. # Sep is the character that separates the elements of the list.
  1329. # Output variables:
  1330. # Set is the array.
  1331. # Return value: the number of new elements added to the set.
  1332. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  1333.     nFound = 0
  1334.     Num = split(Elements,Names,Sep)
  1335.     for (i = 1; i <= Num; i++) {
  1336.     ind = Names[i]
  1337.     if (!(ind in Set)) {
  1338.         Set[ind]
  1339.         nFound++
  1340.     }
  1341.     }
  1342.     return nFound
  1343. }
  1344.  
  1345. # Returns the number of elements in set Set
  1346. function NumElem(Set,  elem,Num) {
  1347.     for (elem in Set)
  1348.     Num++
  1349.     return Num
  1350. }
  1351.  
  1352. # Remove all elements from Set
  1353. function DeleteAll(Set,  i) {
  1354.     split("",Set,",")
  1355. }
  1356.  
  1357. # Returns a list of all of the elements in Set[], with each pair of elements
  1358. # separated by Sep.
  1359. function set2list(Set,Sep,  list,elem) {
  1360.     for (elem in Set)
  1361.     list = list Sep elem
  1362.     return substr(list,2)    # skip 1st separator
  1363. }
  1364. ### End set library
  1365.